<template>
  <div ref="editorRef" :style="style"></div>
</template>

<script setup>
import { ref, watch, onMounted, onBeforeUnmount, shallowRef } from 'vue'
import { EditorView } from '@codemirror/view'
import { EditorState } from '@codemirror/state'
import { basicSetup } from 'codemirror'

const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  extensions: {
    type: Array,
    default: () => []
  },
  autofocus: {
    type: Boolean,
    default: false
  },
  indentWithTab: {
    type: Boolean,
    default: true
  },
  tabSize: {
    type: Number,
    default: 2
  },
  style: {
    type: Object,
    default: () => ({})
  }
})

const emit = defineEmits(['update:modelValue'])

const editorRef = ref(null)
const view = shallowRef(null)

const createExtensions = () => {
  const exts = [basicSetup]

  // 添加自定义扩展
  if (props.extensions && props.extensions.length) {
    exts.push(...props.extensions)
  }

  // 添加更新监听器
  exts.push(
    EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        const value = update.state.doc.toString()
        emit('update:modelValue', value)
      }
    })
  )

  // 配置缩进
  exts.push(
    EditorState.tabSize.of(props.tabSize)
  )

  return exts
}

const initEditor = () => {
  if (!editorRef.value) return

  const state = EditorState.create({
    doc: props.modelValue,
    extensions: createExtensions()
  })

  view.value = new EditorView({
    state,
    parent: editorRef.value
  })

  if (props.autofocus) {
    view.value.focus()
  }
}

const destroyEditor = () => {
  if (view.value) {
    view.value.destroy()
    view.value = null
  }
}

// 监听 modelValue 变化
watch(() => props.modelValue, (newValue) => {
  if (view.value) {
    const currentValue = view.value.state.doc.toString()
    if (newValue !== currentValue) {
      view.value.dispatch({
        changes: {
          from: 0,
          to: currentValue.length,
          insert: newValue
        }
      })
    }
  }
})

onMounted(() => {
  initEditor()
})

onBeforeUnmount(() => {
  destroyEditor()
})
</script>

<style scoped>
:deep(.cm-editor) {
  height: 100%;
}

:deep(.cm-scroller) {
  overflow: auto;
}
</style>
